/* * Copyright 2002-2005 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package info.jtrac.domain; import info.jtrac.Jtrac; import info.jtrac.domain.FilterCriteria.Expression; import info.jtrac.util.DateUtils; import info.jtrac.wicket.JtracCheckBoxMultipleChoice; import info.jtrac.wicket.yui.YuiCalendar; import static info.jtrac.domain.ColumnHeading.Name.*; import static info.jtrac.domain.FilterCriteria.Expression.*; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.wicket.MarkupContainer; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.model.PropertyModel; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Restrictions; /** * used to render columns in the search results table * and also in the search filter screen */ public class ColumnHeading implements Serializable { private static final Map<String, Name> NAMES_MAP; // set up a static Map to resolve a String to our ColumnHeading.Name enum value static { NAMES_MAP = new HashMap<String, Name>(); for (Name n : Name.values()) { NAMES_MAP.put(n.text, n); } } /** * Resolve a String to a valid enum value for ColumnHeading.Name */ private static Name convertToName(String text) { Name n = NAMES_MAP.get(text); if (n == null) { throw new RuntimeException("Bad name " + text); } return n; } /** * test if a given string is a valid column heading name */ public static boolean isValidName(String text) { return NAMES_MAP.containsKey(text); } public static boolean isValidFieldOrColumnName(String text) { return isValidName(text) || Field.isValidName(text); } public enum Name { ID("id"), SUMMARY("summary"), DETAIL("detail"), LOGGED_BY("loggedBy"), STATUS("status"), ASSIGNED_TO("assignedTo"), TIME_STAMP("timeStamp"), SPACE("space"); private String text; Name(String text) { this.text = text; } public String getText() { return text; } @Override public String toString() { return text; } } private Field field; private Name name; private String label; private boolean visible = true; private Processor processor; private FilterCriteria filterCriteria = new FilterCriteria(); public ColumnHeading(Name name) { this.name = name; if(name == DETAIL || name == SPACE) { visible = false; } processor = getProcessor(); } public ColumnHeading(Field field) { this.field = field; this.label = field.getLabel(); processor = getProcessor(); } public boolean isField() { return field != null; } public boolean isDropDownType() { if(isField()) { return field.isDropDownType(); } else { return name == LOGGED_BY || name == ASSIGNED_TO || name == STATUS; } } public static List<ColumnHeading> getColumnHeadings(Space s) { List<ColumnHeading> list = new ArrayList<ColumnHeading>(); list.add(new ColumnHeading(ID)); list.add(new ColumnHeading(SUMMARY)); list.add(new ColumnHeading(DETAIL)); list.add(new ColumnHeading(STATUS)); list.add(new ColumnHeading(LOGGED_BY)); list.add(new ColumnHeading(ASSIGNED_TO)); for(Field f : s.getMetadata().getFieldList()) { list.add(new ColumnHeading(f)); } list.add(new ColumnHeading(TIME_STAMP)); return list; } public static List<ColumnHeading> getColumnHeadings() { List<ColumnHeading> list = new ArrayList<ColumnHeading>(); list.add(new ColumnHeading(ID)); list.add(new ColumnHeading(SPACE)); list.add(new ColumnHeading(SUMMARY)); list.add(new ColumnHeading(DETAIL)); list.add(new ColumnHeading(LOGGED_BY)); list.add(new ColumnHeading(ASSIGNED_TO)); list.add(new ColumnHeading(TIME_STAMP)); return list; } public List<Expression> getValidFilterExpressions() { return processor.getValidFilterExpressions(); } public Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { return processor.getFilterUiFragment(container, user, space, jtrac); } public void addRestrictions(DetachedCriteria criteria) { processor.addRestrictions(criteria); } public String getAsQueryString() { return processor.getAsQueryString(); } public void loadFromQueryString(String s, User user, Jtrac jtrac) { processor.loadFromQueryString(s, user, jtrac); } /** * also see description below for the private getProcessor() method */ private abstract class Processor implements Serializable { /* return the possible expressions (equals, greater-than etc) to show on filter UI for selection */ abstract List<Expression> getValidFilterExpressions(); /* return the wicket ui fragment that will be shown over ajax (based on selected expression) */ abstract Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac); /* get as hibernate restriction and append to passed in criteria that will be used to query the database */ abstract void addRestrictions(DetachedCriteria criteria); /* return a querystring representation of the filter criteria to create a bookmarkable url */ abstract String getAsQueryString(); /* load a querystring representation and initialize filter critera when acting on a bookmarkable url */ abstract void loadFromQueryString(String s, User user, Jtrac jtrac); } /** * this routine is a massive if-then construct that acts as a factory for the * right implementation of the responsibilities defined in the "Processor" class (above) * based on the type of ColumnHeading - the right implementation will be returned. * having everything in one place below, makes it easy to maintain, as the * logic of each of the methods are closely interdependent for a given column type * for e.g. the kind of hibernate criteria needed depends on what is made available on the UI */ private Processor getProcessor() { if(isField()) { switch(field.getName().getType()) { //============================================================== case 1: case 2: case 3: return new Processor() { List<Expression> getValidFilterExpressions() { return getAsList(IN); } Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { Fragment fragment = new Fragment("fragParent", "multiSelect", container); final Map<String, String> options = field.getOptions(); JtracCheckBoxMultipleChoice choice = new JtracCheckBoxMultipleChoice("values", new ArrayList(options.keySet()), new IChoiceRenderer() { public Object getDisplayValue(Object o) { return options.get(o); } public String getIdValue(Object o, int i) { return o.toString(); } }); fragment.add(choice); choice.setModel(new PropertyModel(filterCriteria, "values")); return fragment; } void addRestrictions(DetachedCriteria criteria) { if(filterHasValueList()) { List values = filterCriteria.getValues(); List<Integer> keys = new ArrayList<Integer>(values.size()); for(Object o : values) { keys.add(new Integer(o.toString())); } criteria.add(Restrictions.in(getNameText(), keys)); } } String getAsQueryString() { return getQueryStringFromValueList(); } void loadFromQueryString(String s, User user, Jtrac jtrac) { setValueListFromQueryString(s); } }; //============================================================== case 4: // decimal number return new Processor() { List<Expression> getValidFilterExpressions() { return getAsList(EQ, NOT_EQ, GT, LT, BETWEEN); } Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { Fragment fragment = new Fragment("fragParent", "textField", container); TextField textField = new TextField("value", Double.class); textField.setModel(new PropertyModel(filterCriteria, "value")); fragment.add(textField); if(filterCriteria.getExpression() == BETWEEN) { TextField textField2 = new TextField("value2", Double.class); textField.setModel(new PropertyModel(filterCriteria, "value2")); fragment.add(textField2); } else { fragment.add(new WebMarkupContainer("value2").setVisible(false)); } return fragment; } void addRestrictions(DetachedCriteria criteria) { if(filterHasValue()) { Object value = filterCriteria.getValue(); switch(filterCriteria.getExpression()) { case EQ: criteria.add(Restrictions.eq(getNameText(), value)); break; case NOT_EQ: criteria.add(Restrictions.not(Restrictions.eq(name.text, value))); break; case GT: criteria.add(Restrictions.gt(getNameText(), value)); break; case LT: criteria.add(Restrictions.lt(getNameText(), value)); break; case BETWEEN: criteria.add(Restrictions.gt(getNameText(), value)); criteria.add(Restrictions.lt(getNameText(), filterCriteria.getValue2())); break; default: } } } String getAsQueryString() { return getQueryStringFromValue(Double.class); } void loadFromQueryString(String s, User user, Jtrac jtrac) { setValueFromQueryString(s, Double.class); } }; //============================================================== case 6: // date return new Processor() { List<Expression> getValidFilterExpressions() { return getAsList(EQ, NOT_EQ, GT, LT, BETWEEN); } Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { Fragment fragment = new Fragment("fragParent", "dateField", container); YuiCalendar calendar = new YuiCalendar("value", new PropertyModel(filterCriteria, "value"), false); fragment.add(calendar); if(filterCriteria.getExpression() == BETWEEN) { YuiCalendar calendar2 = new YuiCalendar("value2", new PropertyModel(filterCriteria, "value2"), false); fragment.add(calendar2); } else { fragment.add(new WebMarkupContainer("value2").setVisible(false)); } return fragment; } void addRestrictions(DetachedCriteria criteria) { if(filterHasValue()) { Object value = filterCriteria.getValue(); switch(filterCriteria.getExpression()) { case EQ: criteria.add(Restrictions.eq(getNameText(), value)); break; case NOT_EQ: criteria.add(Restrictions.not(Restrictions.eq(getNameText(), value))); break; case GT: criteria.add(Restrictions.gt(getNameText(), value)); break; case LT: criteria.add(Restrictions.lt(getNameText(), value)); break; case BETWEEN: criteria.add(Restrictions.gt(getNameText(), value)); criteria.add(Restrictions.lt(getNameText(), filterCriteria.getValue2())); break; default: } } } String getAsQueryString() { return getQueryStringFromValue(Date.class); } void loadFromQueryString(String s, User user, Jtrac jtrac) { setValueFromQueryString(s, Date.class); } }; //============================================================== case 5: // free text return new Processor() { List<Expression> getValidFilterExpressions() { return getAsList(CONTAINS); } Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { return getTextFieldFragment(container); } void addRestrictions(DetachedCriteria criteria) { if(filterHasValue()) { criteria.add(Restrictions.ilike(getNameText(), (String) filterCriteria.getValue(), MatchMode.ANYWHERE)); } } String getAsQueryString() { return getQueryStringFromValue(String.class); } void loadFromQueryString(String s, User user, Jtrac jtrac) { setValueFromQueryString(s, String.class); } }; //============================================================== default: throw new RuntimeException("Unknown Column Heading " + name); } } else { // this is not a custom field but one of the "built-in" columns switch(name) { //============================================================== case ID: return new Processor() { List<Expression> getValidFilterExpressions() { return getAsList(EQ); } Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { return getTextFieldFragment(container); } void addRestrictions(DetachedCriteria criteria) { if(filterHasValue()) { // should never come here for criteria: see ItemSearch#getRefId() throw new RuntimeException("should not come here for 'id'"); } } String getAsQueryString() { return getQueryStringFromValue(String.class); } void loadFromQueryString(String s, User user, Jtrac jtrac) { setValueFromQueryString(s, String.class); } }; //============================================================== case SUMMARY: return new Processor() { List<Expression> getValidFilterExpressions() { return getAsList(CONTAINS); } Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { return getTextFieldFragment(container); } void addRestrictions(DetachedCriteria criteria) { if(filterHasValue()) { criteria.add(Restrictions.ilike(getNameText(), (String) filterCriteria.getValue(), MatchMode.ANYWHERE)); } } String getAsQueryString() { return getQueryStringFromValue(String.class); } void loadFromQueryString(String s, User user, Jtrac jtrac) { setValueFromQueryString(s, String.class); } }; //============================================================== case DETAIL: return new Processor() { List<Expression> getValidFilterExpressions() { return getAsList(CONTAINS); } Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { return getTextFieldFragment(container); } void addRestrictions(DetachedCriteria criteria) { // do nothing, 'detail' already processed, see: ItemSearch#getSearchText() } String getAsQueryString() { return getQueryStringFromValue(String.class); } void loadFromQueryString(String s, User user, Jtrac jtrac) { setValueFromQueryString(s, String.class); } }; //============================================================== case STATUS: return new Processor() { List<Expression> getValidFilterExpressions() { return getAsList(IN); } Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { Fragment fragment = new Fragment("fragParent", "multiSelect", container); // status selectable only when context space is not null final Map<Integer, String> options = space.getMetadata().getStatesMap(); options.remove(State.NEW); JtracCheckBoxMultipleChoice choice = new JtracCheckBoxMultipleChoice("values", new ArrayList(options.keySet()), new IChoiceRenderer() { public Object getDisplayValue(Object o) { return options.get(o); } public String getIdValue(Object o, int i) { return o.toString(); } }); fragment.add(choice); choice.setModel(new PropertyModel(filterCriteria, "values")); return fragment; } void addRestrictions(DetachedCriteria criteria) { if(filterHasValueList()) { criteria.add(Restrictions.in(getNameText(), filterCriteria.getValues())); } } String getAsQueryString() { return getQueryStringFromValueList(); } void loadFromQueryString(String s, User user, Jtrac jtrac) { setStatusListFromQueryString(s); } }; //============================================================== case ASSIGNED_TO: case LOGGED_BY: return new Processor() { List<Expression> getValidFilterExpressions() { return getAsList(IN); } Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { Fragment fragment = new Fragment("fragParent", "multiSelect", container); List<User> users = null; if(space == null) { users = jtrac.findUsersForUser(user); } else { users = jtrac.findUsersForSpace(space.getId()); } JtracCheckBoxMultipleChoice choice = new JtracCheckBoxMultipleChoice("values", users, new IChoiceRenderer() { public Object getDisplayValue(Object o) { return ((User) o).getName(); } public String getIdValue(Object o, int i) { return ((User) o).getId() + ""; } }); fragment.add(choice); choice.setModel(new PropertyModel(filterCriteria, "values")); return fragment; } void addRestrictions(DetachedCriteria criteria) { if(filterHasValueList()) { criteria.add(Restrictions.in(getNameText(), filterCriteria.getValues())); } } String getAsQueryString() { return getQueryStringFromUserList(); } void loadFromQueryString(String s, User user, Jtrac jtrac) { setUserListFromQueryString(s, jtrac); } }; //============================================================== case TIME_STAMP: return new Processor() { List<Expression> getValidFilterExpressions() { return getAsList(BETWEEN, GT, LT); } Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { Fragment fragment = new Fragment("fragParent", "dateField", container); YuiCalendar calendar = new YuiCalendar("value", new PropertyModel(filterCriteria, "value"), false); fragment.add(calendar); if(filterCriteria.getExpression() == BETWEEN) { YuiCalendar calendar2 = new YuiCalendar("value2", new PropertyModel(filterCriteria, "value2"), false); fragment.add(calendar2); } else { fragment.add(new WebMarkupContainer("value2").setVisible(false)); } return fragment; } void addRestrictions(DetachedCriteria criteria) { if(filterHasValue()) { Object value = filterCriteria.getValue(); switch(filterCriteria.getExpression()) { case GT: criteria.add(Restrictions.gt(getNameText(), value)); break; case LT: criteria.add(Restrictions.lt(getNameText(), value)); break; case BETWEEN: criteria.add(Restrictions.gt(getNameText(), value)); criteria.add(Restrictions.lt(getNameText(), filterCriteria.getValue2())); break; default: } } } String getAsQueryString() { return getQueryStringFromValue(Date.class); } void loadFromQueryString(String s, User user, Jtrac jtrac) { setValueFromQueryString(s, Date.class); } }; //============================================================== case SPACE: return new Processor() { List<Expression> getValidFilterExpressions() { return getAsList(IN); } Fragment getFilterUiFragment(MarkupContainer container, User user, Space space, Jtrac jtrac) { Fragment fragment = new Fragment("fragParent", "multiSelect", container); List<Space> spaces = new ArrayList(user.getSpaces()); JtracCheckBoxMultipleChoice choice = new JtracCheckBoxMultipleChoice("values", spaces, new IChoiceRenderer() { public Object getDisplayValue(Object o) { return ((Space) o).getName(); } public String getIdValue(Object o, int i) { return ((Space) o).getId() + ""; } }); fragment.add(choice); choice.setModel(new PropertyModel(filterCriteria, "values")); return fragment; } void addRestrictions(DetachedCriteria criteria) { // already handled space as special case, see ItemSearch#getSelectedSpaces() } String getAsQueryString() { return getQueryStringFromSpaceList(); } void loadFromQueryString(String s, User user, Jtrac jtrac) { setSpaceListFromQueryString(s, user, jtrac); } }; //============================================================== default: throw new RuntimeException("Unknown Column Heading " + name); } } } private List<Expression> getAsList(Expression... expressions) { List<Expression> list = new ArrayList<Expression>(); for(Expression e : expressions) { list.add(e); } return list; } private Fragment getTextFieldFragment(MarkupContainer container) { Fragment fragment = new Fragment("fragParent", "textField", container); TextField textField = new TextField("value", String.class); textField.setModel(new PropertyModel(filterCriteria, "value")); fragment.add(textField); fragment.add(new WebMarkupContainer("value2").setVisible(false)); return fragment; } private boolean filterHasValueList() { if(filterCriteria.getExpression() != null && filterCriteria.getValues() != null && filterCriteria.getValues().size() > 0) { return true; } return false; } private boolean filterHasValue() { Object value = filterCriteria.getValue(); if(filterCriteria.getExpression() != null && value != null && value.toString().trim().length() > 0) { return true; } return false; } private String prependExpression(String s) { return filterCriteria.getExpression().getKey() + "_" + s; } private String getQueryStringFromValueList() { if(!filterHasValueList()) { return null; } String temp = ""; for(Object o : filterCriteria.getValues()) { if(temp.length() > 0) { temp = temp + "_"; } temp = temp + o; } return prependExpression(temp); } private String getQueryStringFromValue(Class clazz) { if(!filterHasValue()) { return null; } String temp = ""; if(clazz.equals(Date.class)) { temp = DateUtils.format((Date) filterCriteria.getValue()); if(filterCriteria.getValue2() != null) { temp = temp + "_" + DateUtils.format((Date) filterCriteria.getValue2()); } } else { temp = filterCriteria.getValue() + ""; if(filterCriteria.getValue2() != null) { temp = temp + "_" + filterCriteria.getValue2(); } } return prependExpression(temp); } // TODO refactor code duplication private String getQueryStringFromUserList() { if(!filterHasValueList()) { return null; } String temp = ""; for(User u : (List<User>) filterCriteria.getValues()) { if(temp.length() > 0) { temp = temp + "_"; } temp = temp + u.getId(); } return prependExpression(temp); } // TODO refactor code duplication private String getQueryStringFromSpaceList() { if(!filterHasValueList()) { return null; } String temp = ""; for(Space s : (List<Space>) filterCriteria.getValues()) { if(temp.length() > 0) { temp = temp + "_"; } temp = temp + s.getId(); } return prependExpression(temp); } private List<String> setExpressionAndGetRemainingTokens(String s) { String [] tokens = s.split("_"); filterCriteria.setExpression(FilterCriteria.convertToExpression(tokens[0])); List<String> remainingTokens = new ArrayList<String>(); // ignore first token, this has been parsed as Expression above for(int i = 1; i < tokens.length; i++ ) { remainingTokens.add(tokens[i]); } return remainingTokens; } private void setValueListFromQueryString(String raw) { filterCriteria.setValues(setExpressionAndGetRemainingTokens(raw)); } // TODO refactor with more methods in filtercriteria private void setValueFromQueryString(String raw, Class clazz) { List<String> tokens = setExpressionAndGetRemainingTokens(raw); String v1 = tokens.get(0); String v2 = tokens.size() > 1 ? tokens.get(1) : null; if(clazz.equals(Double.class)) { filterCriteria.setValue(new Double(v1)); if(v2 != null) { filterCriteria.setValue2(new Double(v2)); } } else if(clazz.equals(Date.class)) { filterCriteria.setValue(DateUtils.convert(v1)); if(v2 != null) { filterCriteria.setValue2(DateUtils.convert(v2)); } } else { // String filterCriteria.setValue(v1); if(v2 != null) { filterCriteria.setValue2(v2); } } } private void setUserListFromQueryString(String raw, Jtrac jtrac) { List<String> tokens = setExpressionAndGetRemainingTokens(raw); List<User> users = jtrac.findUsersWhereIdIn(getAsListOfLong(tokens)); filterCriteria.setValues(users); } private void setSpaceListFromQueryString(String raw, User user, Jtrac jtrac) { List<String> tokens = setExpressionAndGetRemainingTokens(raw); List<Space> temp = jtrac.findSpacesWhereIdIn(getAsListOfLong(tokens)); // for security, prevent URL spoofing to show spaces not allocated to user List<Space> spaces = new ArrayList<Space>(); for(Space s : temp) { if(user.isAllocatedToSpace(s.getId())) { spaces.add(s); } } filterCriteria.setValues(spaces); } private void setStatusListFromQueryString(String raw) { List<String> tokens = setExpressionAndGetRemainingTokens(raw); List<Integer> statuses = new ArrayList<Integer>(); for(String s : tokens) { statuses.add(new Integer(s)); } filterCriteria.setValues(statuses); } private List<Long> getAsListOfLong(List<String> tokens) { List<Long> ids = new ArrayList<Long>(); for(String s : tokens) { ids.add(new Long(s)); } return ids; } /* custom accessor */ public void setName(String nameAsString) { name = convertToName(nameAsString); } public String getNameText() { if(isField()) { return field.getName().getText(); } return name.text; } //========================================================================== public Name getName() { return name; } public Field getField() { return field; } public String getLabel() { return label; } public FilterCriteria getFilterCriteria() { return filterCriteria; } public void setFilterCriteria(FilterCriteria filterCriteria) { this.filterCriteria = filterCriteria; } public boolean isVisible() { return visible; } public void setVisible(boolean visible) { this.visible = visible; } @Override public int hashCode() { if(isField()) { return field.hashCode(); } return name.hashCode(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ColumnHeading)) { return false; } final ColumnHeading ch = (ColumnHeading) o; if(ch.isField()) { return ch.field.equals(field); } return ch.name.equals(name); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("name [").append(name); sb.append("]; filterCriteria [").append(filterCriteria); sb.append("]"); return sb.toString(); } }